package data.stock;

import java.io.Serializable;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;

/**
 * The DataHistory object maintains a series of time-stamped pieces of
 * numerical data.  Essentially a time->number map.
 * @author Kevin Dolan
 */
public class DataHistory implements Serializable {

	private static final long serialVersionUID = 2327706380803532634L;
	private TreeMap<Long, Double> data;
	private double minData, maxData;
	
	public DataHistory() {
		data = new TreeMap<Long, Double>();
		minData = Double.MAX_VALUE;
		maxData = -minData;
	}
	
	/**
	 * Add a piece of data to the ticker history
	 * @param time  the time of the data point
	 * @param point the point to be added
	 */
	public void addData(long time, double point) {
		data.put(time, point);
		if(point > maxData)
			maxData = point;
		if(point < minData)
			minData = point;
	}
	
	/**
	 * Add a piece of data to the ticker history
	 * @param date  the date object of the data point
	 * @param point the point to be added
	 */
	public void addData(Date date, double point) {
		long time = date.getTime();
		addData(time, point);
	}
	
	/**
	 * Adds a collection of data to the ticker history
	 * @param newdata  the new collection of data
	 */
	public void addData(Map<Long, Double> newdata) {
		Set<Entry<Long, Double>> entries = newdata.entrySet();
		for(Entry<Long, Double> entry : entries)
			addData(entry.getKey(), entry.getValue());
	}
	
	/**
	 * @return the number of data points
	 */
	public int getNumberDataPoints() {
		return data.size();
	}
	
	/**
	 * @return the appropriate range of entries
	 */
	@SuppressWarnings("unchecked")
	private Entry<Long, Double>[] getRange(long time) {
		if(time > data.lastKey()) {
			Entry<Long, Double> entry = data.lastEntry();
			return new Entry[] { entry, entry };
		}
		if(time < data.firstKey()) {
			Entry<Long, Double> entry = data.firstEntry();
			return new Entry[] { entry, entry };
		}
		return new Entry[] { data.floorEntry(time), data.ceilingEntry(time) };
	}
	
	/**
	 * Determine the (potentially interpolated) data point of a given time
	 * @param time  the time to get the value for
	 * @return 	 	the interpolated data value
	 */
	public double getData(long time) {
		Entry<Long, Double>[] result = getRange(time);
		
		double range;
		
		long x1 = result[0].getKey();
		double y1 = result[0].getValue();
		
		long x2 = result[1].getKey();
		double y2 = result[1].getValue();
		
		range = x2 - x1;
		if(range == 0)
			return y1;
		
		double position = time - x1;
		double proportion = position / range;
		
		range = y2 - y1;
		
		return proportion * range + y1;
	}
	
	/**
	 * Determine the (potentially interpolated) data point of a given date
	 * @param date  the date to get the value for
	 * @return 	 	the interpolated data value
	 */
	public double getData(Date date) {
		return getData(date.getTime());
	}
	
	/**
	 * Normalize the data to be between the min and max
	 * @param data  the value of the data
	 * @return		a number (preferably) [0,1]
	 */
	public double normalizeData(double data) {
		double diff = data - minData;
		double range = maxData - minData;
		return diff / range;
	}
	
	/**
	 * Return all times between the given range
	 * @param begin the beginning of the range
	 * @param end   the end of the range
	 * @return      an ordered set of time values
	 */
	public Set<Long> getTimesBetween(long begin, long end) {
		return data.subMap(begin, end).keySet();
	}
	
	/**
	 * Get the lowest time greater than the input time
	 * @param time the time to look for
	 * @return     the ceiling time
	 */
	public long ceilingTime(long time) {
		return data.ceilingKey(time);
	}
	
	/**
	 * Get the greatest time less than the input time
	 * @param time the time to look for
	 * @return     the floor time
	 */
	public long floorTime(long time) {
		return data.floorKey(time);
	}
	
	/**
	 * @return the first time of this data set
	 */
	public long getFirstTime() {
		return data.firstKey();
	}
	
	/**
	 * @return the last time of this data set
	 */
	public long getLastTime() {
		return data.lastKey();
	}
	
	/**
	 * @return a csv-formatted string containing this data set
	 */
	public String getCsv() {
		StringBuffer sb = new StringBuffer();
		sb.append("Time,Data");
		Set<Entry<Long,Double>> dataSet = data.entrySet();
		for(Entry<Long,Double> dataPoint : dataSet) {
			sb.append("\n" + dataPoint.getKey() + "," + dataPoint.getValue()); 
		}
		return sb.toString();
	}
	
	/**
	 * Get a subset of the data points
	 * @param start the beginning of the range, inclusive
	 * @param end	the end of the range, inclusive
	 * @return		the subset of points within the range
	 */
	public DataHistory getSubHistory(long start, long end) {
		TreeMap<Long, Double> subMap = (TreeMap<Long, Double>) data.subMap(start, true, end, true);
		DataHistory subData = new DataHistory();
		subData.addData(subMap);
		return subData;
	}
}
